package com.nulldev.util.networking.tcp_legacy.server;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import com.nulldev.util.data.Variables;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.networking.ip.IP;
import com.nulldev.util.networking.tcp_legacy.TCPCore;
import com.nulldev.util.networking.tcp_legacy.fRetry;

@Deprecated
abstract public class TCPServer extends fRetry {
	protected final Logger logger = LoggerFactory.getLoggerD(true);
	final CopyOnWriteArrayList<TCPCore> connections = new CopyOnWriteArrayList<TCPCore>();
	private final int port;
	private final boolean localIP;
	private volatile InetAddress ip;
	private volatile ServerSocket server;

	public TCPServer(String category, String name, int port) {
		super(category, name);
		this.port = port;
		this.localIP = true;
	}

	public TCPServer(String category, String name, int port, final boolean localIP) {
		super(category, name);
		this.port = port;
		this.localIP = localIP;
	}

	public boolean isLocal() {
		return this.localIP;
	}

	public int getPort() {
		return this.port;
	}

	public String getIP() {
		if (this.localIP) {
			return "127.0.0.1";
		}
		try {
			return this.ip.getHostAddress();
		} catch (Exception e) {
			try {
				return IP.lanAddress().getHostAddress();
			} catch (Exception e1) {
				return "127.0.0.1";
			}
		}
	}

	private Socket socket;

	protected void retry() {
		try {
			if (this.localIP) {
				server = new ServerSocket();
				server.bind(new InetSocketAddress("127.0.0.1", port));
			} else {
				server = new ServerSocket();
				server.bind(new InetSocketAddress(IP.lanAddress(), port));
			}
		} catch (Exception ex) {
			if (logger.errorEnabled.get())
				logger.error(category, "Unable to start TCP server.", ex);
			failed();
			return;
		}
		try {
			if (this.localIP) {
				if (logger.infoEnabled.get())
					logger.info(category, "Listening on port: " + port);
			} else {
				if (logger.infoEnabled.get())
					logger.info(category, "Listening on ip: " + this.server.getInetAddress().getHostAddress() + ", port: " + port);
			}
			while (running) {
				socket = server.accept();
				socket.setKeepAlive(true);
				this.onSocketCreate(socket);
				success();
				try {
					ServerConnection connection = new ServerConnection(category, name, socket);
					connections.add(connection);
					this.onServerConnection(connection);
					if (logger.traceEnabled.get())
						logger.trace(category, "Client connected: " + socket.getInetAddress() + ":" + socket.getPort());
					connection.start();
					connected(connection);
				} catch (Exception ex) {
					if (logger.errorEnabled.get())
						logger.error(category, "Error configuring client connection.", ex);
				}
			}
		} catch (Exception ex) {
			if (logger.errorEnabled.get())
				logger.error(category, "Unexpected server error.", ex);
			IOUtils.closeQuietly(server);
			failed();
		}
	}

	protected void stopped() {
		IOUtils.closeQuietly(server);
	}

	public void connected(TCPCore connection) {
	}

	public void disconnected(TCPCore connection) {
	}

	public List<TCPCore> getConnections() {
		return connections;
	}

	@SuppressWarnings("unchecked")
	public CopyOnWriteArrayList<ServerConnection> getConnectionsClone() {
		return (CopyOnWriteArrayList<ServerConnection>) connections.clone();
	}

	public Socket getSocket() {
		return this.socket;
	}

	public void send(String message) {
		for (TCPCore connection : connections)
			connection.send(message);
	}

	public boolean sendBlocking(String message) {
		boolean success = true;
		for (TCPCore connection : connections)
			if (!connection.sendBlocking(message))
				success = false;
		return success;
	}

	public boolean sendBlocking(String message, byte[] bytes) {
		return sendBlocking(message, bytes, 0, bytes.length);
	}

	public boolean sendBlocking(String message, byte[] bytes, int offset, int count) {
		boolean success = true;
		for (TCPCore connection : connections)
			if (!connection.sendBlocking(message, bytes, offset, count))
				success = false;
		return success;
	}

	abstract public void receive(TCPCore connection, String event, String payload, byte[] bytes, long length, final long readTime);

	public void close() {
		for (TCPCore connection : connections) {
			connection.close();
			disconnected(connection);
		}
		connections.clear();
	}

	public Logger getLogger() {
		return this.logger;
	}

	public class ServerConnection extends TCPCore {

		public ServerConnection(String category, String name, Socket socket) throws IOException {
			super(category, name, socket);
		}

		public boolean isValid() {
			if (connections.contains(this))
				return true;
			return false;
		}

		public void receive(String event, String payload, byte[] bytes, long count, final long readTime) {
			TCPServer.this.receive(this, event, payload, bytes, count, readTime);
		}

		public boolean close() {
			super.close();
			connections.remove(this);
			return true;
		}

		private String key = "";

		public String getKey() {
			return this.key;
		}

		public void setKey(final String key) {
			this.key = key;
		}

		public String getIP() {
			if (Variables.notNull(TCPServer.this.ip)) {
				return TCPServer.this.ip.getHostAddress();
			} else {
				return "127.0.0.1";
			}
		}

		public int getPort() {
			return TCPServer.this.port;
		}

		@SuppressWarnings("unchecked")
		public CopyOnWriteArrayList<ServerConnection> getClients() {
			return (CopyOnWriteArrayList<ServerConnection>) TCPServer.this.connections.clone();
		}

		public TCPServer getTCPServer() {
			return TCPServer.this;
		}

		private byte[] publicKey;

		public void setPublicKey(final byte[] keyData) {
			if (keyData != null) {
				this.publicKey = keyData;
			}
		}

		public boolean hasPublicKey() {
			return this.publicKey != null;
		}

		public byte[] getPublicKey() {
			return this.publicKey;
		}
	}

	public void onSocketCreate(final Socket socket) {
	}

	public void onServerConnection(final ServerConnection conn) {
	}
}
